/* vcc/main.c 
 * 
 * This file is part of vcc. 
 * 
 * vcc is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version. 
 * 
 * vcc is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with vcc. If not, see <https://www.gnu.org/licenses/>
 */ 




#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <locale.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <poll.h>
#include <signal.h>
#include <getopt.h>
#include <libintl.h>
#include <limits.h>

#include <klist.h>
#include <vcc/vcc.h>
#include <vcc/version.h>
#include <vcc/robot.h>
#include <vcc/plugin.h>
#include <vcc/pretty.h>
#include <vcc/defserv.h>
#include <vcc/config.h>
#include <vcc/interfaces.h>

#include <readconf/readconf.h>


/* we tried to use libreadline, but after using that, we feel strange 
 * because... because of what? i don't know. */


int 		fd;

/* normally. it belongs to the foreground pgroup, all messages write to terminal. */
/* robot mode. we run in the background pgroup, and give all the messages to the plugin specified. */
int 		mode = MODE_NORMAL;

char 		usrname[USRNAME_SIZE];
struct klist_node delayed_pins = KLIST_NODE_INIT(&delayed_pins);

static char const *help_msg = {
	"Usage: vcc [-auprh] [server]\n"
	"Options: \n"
	"  -u    --user    Set the username on the server. "
	"If unspecified, you must input it interactively. \n"
	"  -p    --port    Specify the port on the server. "
	"Default port is 46. You must keep this as same "
	"as the port on the server. \n"
	"  -r    --robot   Enter the robot mode. \n"
	"  -a    --address Address of the server. "
	"As default, server is %s. \n"
	"  -l    --plugin  Load plugin automatically. \n"
	"  -h    --help    Show this help information. \n"
};

static struct option longopts[] = {
	{
		.name = "address", 
		.has_arg = 1, 
		.flag = NULL, 
		.val = 'a', 
	}, 

	{
		.name = "user", 
		.has_arg = 1, 
		.flag = NULL, 
		.val = 'u'
	}, 

	{
		.name = "port", 
		.has_arg = 1, 
		.flag = NULL, 
		.val = 'p'
	}, 

	{
		.name = "robot", 
		.has_arg = 0, 
		.flag = NULL, 
		.val = 'r'
	}, 

	{
		.name = "plugin", 
		.has_arg = 1, 
		.flag = NULL, 
		.val = 'l'
	}, 

	{
		.name = "help", 
		.has_arg = 0, 
		.flag = NULL, 
		.val = 'h'
	}, 

	{
		NULL, 0, NULL, 0
	}
};


static int usage(void) {
	fprintf(stderr, _(help_msg), VCC_DEFSERV);

	return 1;
}


static int login(int got_usrname) {
	char *passwd;

	if (!got_usrname) {
		fprintf(stderr, _("login as: "));
		scanf("%s", usrname);
	}

	passwd = getpass(_("password: "));

	vcc_login(usrname, passwd);
	uinfo_lself(2, NULL);

	return 0;
}

/* vos, the vcc over ssh, allows user to use vcc without install it. 
 * you can use 'ssh username@example.com' and you will get login just like 
 * using ssh but you can use vcc over that. 
 * */

int vos_login(void) {
	char *passwd, *key, *iv, *name;

	if (!(name = getenv("VOS_USRNAME"))) 
		return 0;

	strcpy(usrname, name);

	if (!(passwd = getenv("VOS_PASSWD"))) {
		fprintf(stderr, _("Password not specified. \n"));

		exit(EXIT_FAILURE);
	}

	vcc_login(usrname, passwd);
	uinfo_lself(2, NULL);

	show_algo_info();

	/* crypt_init will copy that into a heap buffer */

	if (!(key = getenv("VOS_KEY")) || !(iv = getenv("VOS_IV"))) {
		fprintf(stderr, _("warning: crypting disabled. \n"));
		crypt_disabled = 1;

		return 1;
	}

	crypt_init(key, iv);

	return 1;
}


int join_delayed_pins(char *name) {
	struct delayed_pins *dp;

	if (unlikely(!(dp = malloc(sizeof(struct delayed_pins))))) {
		fprintf(stderr, "*** malloc() failed\n");

		return 1;
	}

	klist_init(&dp->node);
	klist_add(&delayed_pins, &dp->node);

	dp->name = strdup(name);
	return 0;
}

int insert_delayed_plugins(void) {
	struct klist_node *n, *next;
	struct delayed_pins *dp;

	for (n = delayed_pins.next; n != &delayed_pins; n = next) {
		next = n->next;
		dp = dpins_of(n);

		insert_plugin(dp->name);
		free(dp->name);
		free(dp);
	}

	return 0;
}

int parse_args(int argc, char **argv, int *port, char **ip) {
	int i, c, got_usrname = 0;

	while ((c = getopt_long(argc, argv, "a:u:p:rl:h", longopts, &i)) != -1) {
		switch (c) {
		case 'a':
			*ip = strdup(optarg);
			printf("%s\n", *ip);

			break;

		case 'u':
			strcpy(usrname, optarg);
			got_usrname = 1;

			printf("%s\n", usrname);

			break;

		case 'p':
			*port = atoi(optarg);

			break;

		case 'r':
			mode = MODE_ROBOT;
			break;

		case 'l':
			join_delayed_pins(optarg);
			
			break;

		case 'h':
		default:
			exit(usage());
		}
	}

	return got_usrname;
}


int handle_command(char *buf) {
	switch (*buf) {
	case '\n':
		break;

	case '-':
		return do_cmd(buf);
	
	default:
		do_hook_send(buf);

		send_msg(buf, usrname);
		pretty_new_msg(usrname, buf, current_sid, 0);

		break;
	}

	return 0;
}

int do_handle_command(char *buf) {
	handle_command(buf);
	pretty_prompt(usrname);

	return 0;
}

void sigint_handler(int signr) {
	(void) signr;

	printf("\n");
	pretty_prompt(usrname);
}


int new_msg(char *msg, char *name, int session, int flags) {
	do_hook_recv(msg, name, session);

	if (mode == MODE_NORMAL) {
		if (*msg) 
			pretty_new_msg(name, msg, session, flags);
		pretty_prompt(usrname);
	}

	return 0;
}


int do_new_request(struct vcc_relay_header *hdr) {
	struct vcc_request req;
	int flags;
	char *msg;

	if (ntohl(hdr->magic) == VCC_MAGIC) {
		memcpy(&req, hdr, sizeof(struct vcc_relay_header));
		read(fd, (void *) &req + sizeof(struct vcc_relay_header), 
				REQ_SIZE - sizeof(struct vcc_relay_header));

		/* classical process */

		if (ntohl(req.reqtype) == REQ_MSG_NEW) {
			if (ntohl(req.flags) & FLAG_ENCRYPTED) 
				decrypt(req.msg);

			new_msg(req.msg, req.usrname, ntohl(req.session), 0);
		}

		else 
			do_bh(&req);
	}

	else {
		if (ntohl(hdr->reqtype) == REQ_REL_NEW) {
			flags = MSG_NEW_RELAY;

			if (hdr->uid) 
				flags |= MSG_NEW_ONLY_VISIBLE;

			msg = relay_get_msg(fd, hdr);
			new_msg(msg, hdr->usrname, ntohl(hdr->session), flags);

			free(msg);
		}

		else 
			do_relay_bh(hdr);
	}

	return 0;
}

int vcc_show_version(void) {
	int pid;

	printf("%s\n", VCC_VERSION);
	fflush(stdout);

	if (!(pid = fork())) {
		execl("/etc/vccrc", VCC_VERSION, VCC_SEMVER, NULL);

		exit(1);
	}

	wait4(pid, NULL, 0, NULL);

	return 0;
}


int init_signal(void) {
	struct sigaction act;

	memset(&act, 0, sizeof(struct sigaction));
	act.sa_handler = sigint_handler;
	
	sigaction(SIGINT, &act, NULL);

	return 0;
}

static int vcc_init_config(void) {
	if (openconf()) 
		return 1;

	readconf_init();
	return 0;
}


int main(int argc, char **argv) {
	struct pollfd 		fds[2];
	struct vcc_request 	*req;
	struct vcc_relay_header *hdr;
	char 			*buf, *ip = VCC_DEFSERV;
	char 			key[64], iv[64];
	int 			n, size, port = VCC_PORT;

	(void) argc;

	setlocale(LC_ALL, "");

	bindtextdomain("vcc", "lang");
	textdomain("vcc");

	vcc_init_config();

	n = parse_args(argc, argv, &port, &ip);
	vcc_show_version();
	init_socket(ip, port);

	printf(_("Connected to server. \n"));

	if (mode == MODE_ROBOT) 
		return robot();	

	if (!vos_login() && !do_intiauth()) {
		/* normally startup */
		login(n);
		show_algo_info();

		memset(key, 0, 64);
		memset(iv, 0, 64);

		fprintf(stderr, "Key: ");
		read(0, key, 64);

		fprintf(stderr, "IV: ");
		read(0, iv, 64);

		crypt_init(key, iv);
	}
	
	fds[0].fd = 0;
	fds[0].events = POLLIN;
	fds[0].revents = 0;

	fds[1].fd = fd;
	fds[1].events = POLLIN;
	fds[1].revents = 0;

	pretty_init();
	init_signal();

	insert_delayed_plugins();

	if (unlikely(!(buf = malloc(PATH_MAX)))) {
		fprintf(stderr, "*** malloc() failed\n");

		return 1;
	}

	if (unlikely(!(hdr = malloc(sizeof(struct vcc_relay_header))))) {
		fprintf(stderr, "*** malloc() failed\n");

		return 1;
	}

	if (unlikely(!(req = malloc(sizeof(struct vcc_request ))))) {
		fprintf(stderr, "*** malloc() failed\n");

		return 1;
	}

	for (;;) {
		memset(buf, 0, MSG_SIZE);
		n = poll(fds, 2, -1);

		if (n == -1) {
			if (likely(errno == EINTR)) 
				continue;

			perror("poll()");

			break;
		}

		if (fds[0].revents & POLLIN) {
			if (read(0, buf, PATH_MAX) == -1) 
				continue;

			do_handle_command(buf);
		}

		if (fds[1].revents & POLLIN) {
			size = read(fd, hdr, sizeof(struct vcc_relay_header));

			if (unlikely(!size)) {
				fprintf(stderr, _("Server closed. \n"));

				break;
			}

			do_new_request(hdr);
		}
	}

	printf(_("bye. \n"));

	return 0;
}

